#include "StdAfx.h"
#include "CoreEngine.h"
#include "FEngineType.h"
#include "Node.h"
#include "Camera.h"

using namespace std;

namespace FEngine
{
	Camera::Camera()
	{
		mPosition			= Vector3(0.0f, 0.0f, 0.0f);
		mLookAt				= Vector3(0.0f, 0.0f, 1.0f);
		mNearViewDist		= 1.0f;
		mFarViewDist		= 750.0f;
		
		_calculateProjMatrix();
		_calculateViewMatrix();
	}

	Camera::~Camera()
	{
	}

	void Camera::setPosition(const Vector3& _pos)
	{
		mPosition = _pos;
		_calculateViewMatrix();
	}

	Vector3 Camera::getPosition() const
	{
		return mPosition;
	}
	
	void Camera::lookAt(const Vector3& _pos)
	{
		mLookAt = _pos;
		_calculateViewMatrix();
	}

	Vector3 Camera::getLookAt() const
	{
		return mLookAt;
	}
	
	void Camera::setNearDistanceView(float _view)
	{
		mNearViewDist = _view;
		_calculateProjMatrix();
	}

	float Camera::getNearDistanceView() const
	{
		return mNearViewDist;
	}
	
	void Camera::setFarDistanceView(float _view)
	{
		mFarViewDist = _view;
		_calculateProjMatrix();
	}

	float Camera::getFarDistanceView() const
	{
		return mFarViewDist;
	}

	Matrix4 Camera::_getMatrixView() const
	{
		return mViewMatrix;
	}

	Matrix4 Camera::_getMatrixProjection() const
	{
		return mProjectionMatrix;
	}
	
	NodeVisibility Camera::isVisible(AABB* _box, bool _useCustomMaxView)
	{
		if (_useCustomMaxView)
			return _customIsVisible(_box);

		// @Kuafu

		// Test assumes frustum planes face inward.
		Vector3 P, Q;
		bool hasPartial = false;

		//		N *Q					 *P
		//		| /						/
		//		|/					   /
		// -----/----- Plane	 -----/----- Plane
		//	   /					 / |
		//	  /						/  |
		//  *P					  *Q   N
		//
		// PQ forms diagonal most closely aligned with plane normal.
		// For each frustum plane, find the box diagonal (there are
		// four main diagonals that intersect the box center point)
		// that points in the same direction as the normal along each
		// axis (i.e., the diagonal that is most aligned with the
		// plane normal). Then test if the box is in front of the
		// plane or not.

		for(int i = 0; i < 6; ++i)
		{
			// For each coordinate axis x, y, z...
			for(int j = 0; j < 3; ++j)
			{
				// Make PQ point in the same direction as
				// the plane normal on this axis.
				if(mFrustumPlanes[i][j] >= 0.0f)
				{
					P[j] = _box->getMinPoint()[j];
					Q[j] = _box->getMaxPoint()[j];
				}
				else
				{
					P[j] = _box->getMaxPoint()[j];
					Q[j] = _box->getMinPoint()[j];
				}
			}

			// If box is in negative half-space, it is behind
			// the plane, and thus, completely outside the
			// frustum. Note that because PQ points roughly in
			// the direction of the plane normal, we can deduce
			// that if Q is outside, then P is also outside--thus we
			// only need to test Q.
			// outside
	//		if(D3DXPlaneDotCoord(&mFrustumPlanes[i], &Q) < 0.0f)
	//			return VISIBILITY_NONE;
			if (D3DXPlaneDotCoord(&mFrustumPlanes[i], &Q) < 0.0f)
				return VISIBILITY_NONE;
			if (D3DXPlaneDotCoord(&mFrustumPlanes[i], &P) < 0.0f)
				hasPartial = true;
		}

		// If we got here, then the AABB is not in the negative
		// space of any of the six frustums; therefore, it must
		// intersect the frustum.
		if (hasPartial)
			return VISIBILITY_PARTIAL;
		
		return VISIBILITY_TOTAL;
	}
	
	NodeVisibility Camera::_customIsVisible(AABB* _box)
	{
		// See isVisible() for explanation

		Vector3 P, Q;
		bool hasPartial = false;

		for(int i = 0; i < 6; ++i)
		{
			for(int j = 0; j < 3; ++j)
			{
				if(mCustomFrustumPlanes[i][j] >= 0.0f)
				{
					P[j] = _box->getMinPoint()[j];
					Q[j] = _box->getMaxPoint()[j];
				}
				else
				{
					P[j] = _box->getMaxPoint()[j];
					Q[j] = _box->getMinPoint()[j];
				}
			}

			if (D3DXPlaneDotCoord(&mCustomFrustumPlanes[i], &Q) < 0.0f)
				return VISIBILITY_NONE;
			if (D3DXPlaneDotCoord(&mCustomFrustumPlanes[i], &P) < 0.0f)
				hasPartial = true;
		}

		if (hasPartial)
			return VISIBILITY_PARTIAL;
		
		return VISIBILITY_TOTAL;
	}
	
	void Camera::_calculateViewMatrix()
	{
		D3DXMatrixLookAtLH(&mViewMatrix,
							&mPosition,							// Position
							&mLookAt,							// LookAt
							&Vector3(0.0f, 1.0f, 0.0f));		// Up vector

		_calculateFrustumPlane();
	}
	
	void Camera::_calculateProjMatrix()
	{
		D3DXMatrixPerspectiveFovLH(&mProjectionMatrix,
								   D3DXToRadian(45),							// Horizontal field of view
								   (float)CoreEngine::getInstance()->getScreenWidth() / CoreEngine::getInstance()->getScreenHeight(),	// Aspect ratio
								   mNearViewDist,								// Near view-plane
								   mFarViewDist);								// Far view-plane

		_calculateFrustumPlane();
	}

	void Camera::_setCustomFrustumCulling(float _maxViewDist)
	{
		Matrix4 customProjMatrix;
		D3DXMatrixPerspectiveFovLH(&customProjMatrix,
								   D3DXToRadian(45),							// Horizontal field of view
								   (float)CoreEngine::getInstance()->getScreenWidth() / CoreEngine::getInstance()->getScreenHeight(),	// Aspect ratio
								   mNearViewDist,								// Near view-plane
								   _maxViewDist);								// Far view-plane

		Matrix4 matrixViewProj = mViewMatrix * customProjMatrix;

		Vector4 col0(matrixViewProj(0, 0), matrixViewProj(1, 0), matrixViewProj(2, 0), matrixViewProj(3, 0));
		Vector4 col1(matrixViewProj(0, 1), matrixViewProj(1, 1), matrixViewProj(2, 1), matrixViewProj(3, 1));
		Vector4 col2(matrixViewProj(0, 2), matrixViewProj(1, 2), matrixViewProj(2, 2), matrixViewProj(3, 2));
		Vector4 col3(matrixViewProj(0, 3), matrixViewProj(1, 3), matrixViewProj(2, 3), matrixViewProj(3, 3));

		// Create our planes with the face inward
		mCustomFrustumPlanes[PLANE_NEAR]		= (Plane)(col2);
		mCustomFrustumPlanes[PLANE_FAR]			= (Plane)(col3 - col2);
		mCustomFrustumPlanes[PLANE_LEFT]		= (Plane)(col3 + col0);
		mCustomFrustumPlanes[PLANE_RIGHT]		= (Plane)(col3 - col0);
		mCustomFrustumPlanes[PLANE_TOP]			= (Plane)(col3 - col1);
		mCustomFrustumPlanes[PLANE_BOTTOM]		= (Plane)(col3 + col1);

		for (int n = 0; n < 6; ++n)
			D3DXPlaneNormalize(&mCustomFrustumPlanes[n], &mCustomFrustumPlanes[n]);
	}

	void Camera::_calculateFrustumPlane()
	{
		Matrix4 matrixViewProj = mViewMatrix * mProjectionMatrix;

		Vector4 col0(matrixViewProj(0, 0), matrixViewProj(1, 0), matrixViewProj(2, 0), matrixViewProj(3, 0));
		Vector4 col1(matrixViewProj(0, 1), matrixViewProj(1, 1), matrixViewProj(2, 1), matrixViewProj(3, 1));
		Vector4 col2(matrixViewProj(0, 2), matrixViewProj(1, 2), matrixViewProj(2, 2), matrixViewProj(3, 2));
		Vector4 col3(matrixViewProj(0, 3), matrixViewProj(1, 3), matrixViewProj(2, 3), matrixViewProj(3, 3));

		// Create our planes with the face inward
		mFrustumPlanes[PLANE_NEAR]		= (Plane)(col2);
		mFrustumPlanes[PLANE_FAR]		= (Plane)(col3 - col2);
		mFrustumPlanes[PLANE_LEFT]		= (Plane)(col3 + col0);
		mFrustumPlanes[PLANE_RIGHT]		= (Plane)(col3 - col0);
		mFrustumPlanes[PLANE_TOP]		= (Plane)(col3 - col1);
		mFrustumPlanes[PLANE_BOTTOM]	= (Plane)(col3 + col1);

		for (int n = 0; n < 6; ++n)
			D3DXPlaneNormalize(&mFrustumPlanes[n], &mFrustumPlanes[n]);
	}

} // namespace FEngine
